package cn.com.netedge.iot.frontend.comm.protocol;

import cn.com.netedge.iot.common.dataobject.ReceiveFrame;
import cn.com.netedge.iot.common.enums.ReceiveFrameTypeEnum;
import cn.com.netedge.iot.common.utils.ProtocolUtils;
import cn.com.netedge.iot.protocol.dlms.base.datatypes.CosemDate;
import cn.com.netedge.iot.protocol.dlms.base.datatypes.CosemDateTime;
import cn.com.netedge.iot.protocol.dlms.base.datatypes.CosemTime;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Date;

public class ProtocolDlms {
    private static final Logger logger = LoggerFactory.getLogger(ProtocolDlms.class);
    //帧长度最小12字节
    private final static int LENGTH_MIN = 8;
    //帧长度最大2k
    private final static int LENGTH_MAX = 2048;

    public static ReceiveFrame validateMsg(ByteBuf inByteBuf) {
        while (inByteBuf.readableBytes() > 0) {
            // 记住(标记)起始位置
            inByteBuf.markReaderIndex();
            if (inByteBuf.readableBytes() < LENGTH_MIN) {
                // 长度不足读指针归位
                inByteBuf.resetReaderIndex();
                // 丢弃一个字节
                inByteBuf.readByte();
                return null;
            }
            if (inByteBuf.readByte() != 0x00) {
                logger.info("Message does not start with 0x00 as expected in by the wrapper header.");
                // 读指针归位
                inByteBuf.resetReaderIndex();
                // 丢弃一个字节
                inByteBuf.readByte();
                continue;
            }
            byte version = inByteBuf.readByte();
            if (version != 1) {
                logger.info("Header version was {}, this stack is only compatible to version 1.", version);
                // 读指针归位
                inByteBuf.resetReaderIndex();
                // 丢弃一个字节
                inByteBuf.readByte();
                continue;
            }
            int sourceWPort = inByteBuf.readUnsignedShort();
            int destinationWPort = inByteBuf.readUnsignedShort();
            int length = inByteBuf.readUnsignedShort();
            logger.info("protocolBase message length:{}", length);
            if (length < 0) {
                // 长度异常指针归位
                inByteBuf.resetReaderIndex();
                // 丢弃一个字节
                inByteBuf.readByte();
                continue;
            }
            //长度超过2K则认为长度错误
            if (length > LENGTH_MAX) {
                // 长度异常指针归位
                inByteBuf.resetReaderIndex();
                // 丢弃一个字节
                inByteBuf.readByte();
                continue;
            }
            // 长度不够
            if (inByteBuf.readableBytes() < length) {
                // 长度不足读指针归位
                inByteBuf.resetReaderIndex();
                return null;
            }
            byte[] msg = new byte[length + 8];
            inByteBuf.resetReaderIndex();
            inByteBuf.readBytes(msg);

            ReceiveFrame frame = new ReceiveFrame();
            frame.setRawData(msg);
            byte[] messageData = new byte[length];
            System.arraycopy(msg, 8, messageData, 0, length);
            frame.setReceiveBuffer(messageData);
            return frame;
        }
        return null;
    }

    public static LoginResult handleLogin(byte[] byteArray) {
        LoginResult result = new LoginResult();
        result.setPass(false);
        if (byteArray.length < 7) {
            logger.info("message length is less than 5, not a valid message.");
            return result;
        }
        ByteBuf byteBuf = Unpooled.wrappedBuffer(byteArray);
        byte frameType = byteBuf.readByte();

        byteBuf.readByte();
        byte deviceType = byteBuf.readByte();
        if (deviceType == 0x11) {
            result.setDeviceType(1);
        } else if (deviceType == 0x12) {
            result.setDeviceType(0);
        }
        byte deviceNumLen = byteBuf.readByte();
        byte[] dNumberBytes = new byte[deviceNumLen];
        byteBuf.readBytes(dNumberBytes);
        String dNumber = new String(dNumberBytes, StandardCharsets.US_ASCII);
        result.setDeviceNumber(dNumber);
        if(!(frameType == (byte) 0xF0 || frameType == (byte) 0xE0)) {
            logger.info("message type is not login or heartbeat.");
            switch (frameType) {
                case (byte) 0xEF:
                    result.setReply(new byte[]{0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, (byte) 0xF3, 0x00});
                    break;
                case (byte) 0xF4:
                    byte deviceLength = (byte) dNumberBytes.length;
                    byte[] m1 = new byte[]{(byte) 0xF5, 0x00, 0x09, deviceLength};
                    int totalLength = deviceLength + m1.length;
                    byte[] head = new byte[]{0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, (byte) totalLength};
                    result.setReply(mergeBytes(head, m1, dNumberBytes));
                    break;
            }
            return result;
        }
        if (frameType == (byte) 0xF0) { //登录帧处理
            result.setFrameType(ReceiveFrameTypeEnum.LOGIN);
            byte functionOption = byteBuf.readByte();
            if (functionOption == 0x10) {  /* 是否回复带时间标识,如果是则回复帧中携带时标*/
                Date date = new Date();
                CosemDateTime cosemDateTime = convertDateToCosemDateTime(date);
                byte[] ResponseBuffer = (new byte[]{0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0C, (byte) 0xF1, 0x00, 0x13, 0x00});
                result.setReply(byteMerger(ResponseBuffer, cosemDateTime.getEncodeDateTime()));
            } else {
                result.setReply(new byte[]{0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, (byte) 0xF1, 0x00, 0x13, 0x00});
            }
            if (byteBuf.readableBytes() >= 2) { //如果剩余字节数量大于2.则登录携带有PANID
                byte[] tmpByte = new byte[2];
                byteBuf.readBytes(tmpByte);
                result.setPanid(ProtocolUtils.printMsg(tmpByte));
            }
        } else if (frameType == (byte) 0xE0) {/*心跳帧处理*/
            result.setFrameType(ReceiveFrameTypeEnum.HEARTBEAT);
            result.setReply(new byte[]{0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, (byte) 0xE1, 0x00, 0x13, 0x00});
        }
        result.setPass(true);
        return result;

    }

    public static CosemDateTime convertDateToCosemDateTime(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        //下标从0开始，所以获取月数需要增加1
        boolean isFirstSunday = (calendar.getFirstDayOfWeek() == Calendar.SUNDAY);
        //获取周几
        int weekDay = calendar.get(Calendar.DAY_OF_WEEK);
        //若一周第一天为星期天，则-1
        if (isFirstSunday) {
            weekDay = weekDay - 1;
            if (weekDay == 0) {
                weekDay = 7;
            }
        }
        CosemDate cosemDate = new CosemDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH), weekDay);
        CosemTime cosemTime = new CosemTime(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND), (calendar.get(Calendar.MILLISECOND) / 10));
        return new CosemDateTime(cosemDate, cosemTime, CosemDateTime.ClockStatus.DAYLIGHT_SAVING_ACTIVE);
    }

    public static byte[] byteMerger(byte[] bt1, byte[] bt2) {
        byte[] bt3 = new byte[bt1.length + bt2.length];
        System.arraycopy(bt1, 0, bt3, 0, bt1.length);
        System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
        return bt3;
    }

    /**
     * 合并多个字节数组到一个字节数组
     *
     * @param values 动态字节数字参数
     * @return byte[] 合并后的字节数字
     */
    private static byte[] mergeBytes(byte[]... values) {
        int lengthByte = 0;
        for (byte[] value : values) {
            lengthByte += value.length;
        }
        byte[] allBytes = new byte[lengthByte];
        int countLength = 0;
        for (byte[] b : values) {
            System.arraycopy(b, 0, allBytes, countLength, b.length);
            countLength += b.length;
        }
        return allBytes;
    }


}
